interface TimelineConfig {
  // 画布的宽度
  width?: number;
  // 画布的高度
  height?: number;
  // 时间轴的日期范围，格式为['YYYY-MM-DD', 'YYYY-MM-DD']
  timeLineRangeDate: [string, string];

  // 时间线的颜色
  lineColor?: string;
  // 时间线的粗细
  lineThickness?: number;

  // 刻度标签字体大小
  fontSize?: number;
  // 刻度线的高度
  tickHeight?: number;
  // 刻度线的粗细
  tickThickness?: number;

  // 刻度标签字体颜色
  fontColor?: string;
  // 主要刻度标签字体颜色（带年份的刻度标签）
  mainFontColor?: string;
  // 刻度标签与时间线的垂直偏移量
  fontOffset?: number;

  // 垂直标线的颜色
  centerLineColor?: string;
  // 垂直标线的高度
  centerLineHeight?: number;
  // 垂直标线的粗细
  centerLineThickness?: number;
  // 垂直标线的垂直偏移量
  centerLineOffset?: number;

  // 事件点日期数组，格式为['YYYY-MM-DD', ...]
  eventPoints?: string[];
  // 事件点的颜色
  eventPointColor?: string;
  // 事件点的大小
  eventPointSize?: number;
}

type DraggedEvent = {
  // 当前最接近的时间点，格式为'YYYY-MM-DD'
  currentDate: string;
  // 当前最接近的事件点，格式为'YYYY-MM-DD'
  eventPoint: string;
  // 左侧当前最接近的事件点，格式为'YYYY-MM-DD'
  leftEventPoint: string;
};
type DraggingEvent = {} & DraggedEvent;

type Events = "dragged" | "dragging";

type EventMap = {
  dragged: DraggedEvent;
  dragging: DraggingEvent;
};

class Timeline {
  // 定义私有变量，存储画布元素和其上下文
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private readonly config: TimelineConfig; // 配置对象
  private parentWidth: number; // 画布父元素的宽度
  private isDragging: boolean = false; // 是否正在拖动
  private startX: number = 0; // 拖动起始点的X坐标
  private offsetX: number = 0; // X轴偏移量
  private firstEventOffsetX: number = 0; // 第一个事件点的X轴偏移量
  private lastEventOffsetX: number = 0; // 最后一个事件点的X轴偏移量
  private eventListeners: {
    [K in Events]?: Array<(data: EventMap[K]) => void>;
  } = {}; // 事件监听器

  // 构造函数，初始化Timeline实例
  constructor(canvasId: string, config: TimelineConfig) {
    this.canvas = document.getElementById(canvasId) as HTMLCanvasElement; // 获取画布元素
    this.ctx = this.canvas.getContext("2d")!; // 获取画布上下文
    this.config = config; // 保存配置对象
    this.parentWidth = this.canvas.parentElement!.offsetWidth; // 获取画布父元素的宽度
    this.canvas.width = this.config?.width
      ? this.config.width
      : this.parentWidth; // 设置画布宽度
    this.canvas.height = config.height || 50; // 设置画布高度

    // 当配置对象中时间轴日期范围为空时，设置默认值
    if (!config.timeLineRangeDate[0]) {
      this.config.timeLineRangeDate[0] = "2024-01-01";
    }
    if (!config.timeLineRangeDate[1]) {
      this.config.timeLineRangeDate[1] = "2024-01-01";
    }

    this.initCanvas(); // 初始化画布
    this.addEventListeners(); // 添加事件监听器
    this.addResizeListener(); // 添加窗口尺寸变化监听器
  }

  // 初始化画布
  private initCanvas() {
    const tickSpacing = this.canvas.width / 13; // 刻度间距
    const startDate = new Date(this.config.timeLineRangeDate[0]);
    const originOffsetX = tickSpacing / 2; // 初始偏移量
    this.offsetX = 0; // 时间轴偏移归零
    this.offsetX += originOffsetX; // 时间轴初始化偏移

    let firstEventPoint = this.config?.eventPoints?.[0];
    let lastEventPoint = this.config?.eventPoints?.slice(-1)[0];
    // 获取第一个事件点和最后一个事件点的日期
    this.config?.eventPoints?.forEach((item) => {
      if (
        new Date(item).getTime() < new Date(firstEventPoint as string).getTime()
      ) {
        firstEventPoint = item;
      }
      if (
        new Date(item).getTime() > new Date(lastEventPoint as string).getTime()
      ) {
        lastEventPoint = item;
      }
    });
    if (firstEventPoint) {
      const firstEventDate = new Date(firstEventPoint);
      const totalYearsOffset =
        (firstEventDate.getFullYear() - startDate.getFullYear()) *
        12 *
        tickSpacing;
      const totalMonthsOffset =
        (firstEventDate.getMonth() - startDate.getMonth()) * tickSpacing;
      const totalDaysOffset = (firstEventDate.getDate() / 30) * tickSpacing;
      this.offsetX -=
        totalYearsOffset +
        totalMonthsOffset +
        totalDaysOffset -
        tickSpacing / 30; // 时间轴偏移至第一个事件点处
      this.firstEventOffsetX = this.offsetX - 1; // 记录第一个事件点的X轴偏移量
    }
    if (lastEventPoint) {
      const lastEventDate = new Date(lastEventPoint);
      const totalYearsOffset =
        (lastEventDate.getFullYear() - startDate.getFullYear()) *
        12 *
        tickSpacing;
      const totalMonthsOffset =
        (lastEventDate.getMonth() - startDate.getMonth()) * tickSpacing;
      const totalDaysOffset = (lastEventDate.getDate() / 30) * tickSpacing;
      this.lastEventOffsetX =
        originOffsetX -
        (totalYearsOffset +
          totalMonthsOffset +
          totalDaysOffset -
          tickSpacing / 30) -
        1; // 记录最后一个事件点的X轴偏移量
    }
    this.drawTimeline(); // 绘制时间线
  }

  // 绘制时间线
  private drawTimeline() {
    const {
      height = 50,
      timeLineRangeDate,

      lineColor = "#D5DAE7",
      lineThickness = 2,

      fontSize = 12,
      tickHeight = 10,
      tickThickness = 2,

      fontColor = "#A2ABCA",
      mainFontColor = "#475996",
      fontOffset = 18,

      centerLineColor = "#2E5CF6",
      centerLineHeight = 25,
      centerLineThickness = 4,
      centerLineOffset = 10,

      eventPoints = [],
      eventPointColor = "#3BD17F",
      eventPointSize = 6,
    } = this.config; // 从配置对象中解构参数

    const months = [
      "1月",
      "2月",
      "3月",
      "4月",
      "5月",
      "6月",
      "7月",
      "8月",
      "9月",
      "10月",
      "11月",
      "12月",
    ]; // 月份数组
    const startDate = new Date(timeLineRangeDate[0]); // 开始日期
    const endDate = new Date(timeLineRangeDate[1]); // 结束日期
    const startYear = startDate.getFullYear(); // 开始年份
    const startMonth = startDate.getMonth(); // 开始月份
    const endYear = endDate.getFullYear(); // 结束年份
    const endMonth = endDate.getMonth(); // 结束月份
    const totalMonths =
      (endYear - startYear) * 12 + endMonth - startMonth + 1 + 12; // 总月份数

    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 清空画布
    this.ctx.lineWidth = lineThickness; // 设置线条宽度
    this.ctx.strokeStyle = lineColor; // 设置线条颜色
    this.ctx.beginPath(); // 开始绘制路径
    this.ctx.moveTo(0, height / 2); // 移动画笔到起始点
    this.ctx.lineTo(this.canvas.width, height / 2); // 绘制主时间线
    this.ctx.stroke(); // 描边

    // 绘制刻度和文本标签
    const tickSpacing = this.canvas.width / 13; // 刻度间距
    for (let i = 0; i < totalMonths; i++) {
      const currentDate = new Date(startYear, startMonth + i - 6, 1); // 当前日期
      this.ctx.lineWidth = tickThickness; // 设置刻度线宽度
      this.ctx.beginPath(); // 开始绘制路径
      this.ctx.moveTo(i * tickSpacing + this.offsetX, height / 2 - tickHeight); // 移动画笔到刻度起点
      this.ctx.lineTo(i * tickSpacing + this.offsetX, height / 2); // 绘制刻度线
      this.ctx.stroke(); // 描边

      const monthIndex = currentDate.getMonth(); // 当前月份索引
      const year = currentDate.getFullYear(); // 当前年份
      const label = (monthIndex === 0 ? `${year}年` : "") + months[monthIndex]; // 标签文本

      this.ctx.font = `${fontSize}px Arial`; // 设置字体
      this.ctx.fillStyle = monthIndex === 0 ? mainFontColor : fontColor; // 设置字体颜色
      this.ctx.fillText(
        label,
        i * tickSpacing + this.offsetX - this.ctx.measureText(label).width / 2,
        height / 2 + tickHeight / 2 + fontOffset
      ); // 绘制标签
    }

    // 绘制垂直标线
    this.ctx.strokeStyle = centerLineColor; // 设置中心线颜色
    this.ctx.lineWidth = centerLineThickness; // 设置中心线宽度
    this.ctx.beginPath(); // 开始绘制路径
    this.ctx.moveTo(this.canvas.width / 2, centerLineOffset); // 移动画笔到中心线起点
    this.ctx.lineTo(this.canvas.width / 2, centerLineOffset + centerLineHeight); // 绘制中心线
    this.ctx.stroke(); // 描边

    // 绘制事件点
    eventPoints?.forEach((event: string) => {
      const eventDate = new Date(event); // 事件日期
      const eventMonthIndex =
        (eventDate.getFullYear() - startYear) * 12 +
        eventDate.getMonth() -
        startMonth; // 事件月份索引
      const eventDay = eventDate.getDate(); // 事件日期
      const xPosition =
        (eventMonthIndex + 6) * tickSpacing +
        ((eventDay - 1) * tickSpacing) / 30 +
        this.offsetX; // 事件点X坐标
      this.ctx.fillStyle = eventPointColor; // 设置事件点颜色
      this.ctx.beginPath(); // 开始绘制路径
      this.ctx.arc(xPosition, height / 2, eventPointSize, 0, Math.PI * 2); // 绘制事件点
      this.ctx.fill(); // 填充
    });
  }

  // 添加事件监听器
  private addEventListeners() {
    this.canvas.addEventListener("mousedown", this.onMouseDown.bind(this)); // 监听鼠标按下事件
    window.addEventListener("mousemove", this.onMouseMove.bind(this)); // 监听鼠标移动事件
    window.addEventListener("mouseup", this.onMouseUp.bind(this)); // 监听鼠标松开事件
  }

  // 添加窗口尺寸变化监听器
  private addResizeListener() {
    window.addEventListener("resize", this.onResize.bind(this));
  }

  // 窗口尺寸变化处理函数
  private onResize() {
    this.parentWidth = this.canvas.parentElement!.offsetWidth; // 更新父元素宽度
    this.canvas.width = this.parentWidth; // 更新画布宽度
    this.initCanvas(); // 重新初始化画布
  }

  // 鼠标按下事件处理函数
  private onMouseDown(event: MouseEvent) {
    this.isDragging = true; // 标记为正在拖动
    this.startX = event.clientX; // 记录起始X坐标
    document.body.style.userSelect = "none"; // 禁止文本选择
  }

  // 鼠标移动事件处理函数
  private onMouseMove(event: MouseEvent) {
    if (this.isDragging) {
      const dx = event.clientX - this.startX; // 计算X轴位移
      this.startX = event.clientX; // 更新起始X坐标
      this.offsetX += dx; // 更新X轴偏移量

      const startDate = new Date(this.config.timeLineRangeDate[0]); // 开始日期
      const endDate = new Date(this.config.timeLineRangeDate[1]); // 结束日期
      const totalMonths =
        (endDate.getFullYear() - startDate.getFullYear()) * 12 +
        endDate.getMonth() -
        startDate.getMonth() +
        1 +
        12; // 总月份数
      const tickSpacing = this.canvas.width / 13; // 刻度间距
      const maxOffsetX = 6 * tickSpacing; // 最大偏移量
      const minOffsetX =
        -totalMonths * tickSpacing + this.canvas.width - 6 * tickSpacing; // 最小偏移量

      // 第一个事件点
      const firstEventPoint = this.config?.eventPoints?.[0];
      if (firstEventPoint) {
        if (this.offsetX > this.firstEventOffsetX) {
          // 限制最大偏移量
          this.offsetX = this.firstEventOffsetX;
        } else if (this.offsetX < this.lastEventOffsetX) {
          // 限制最小偏移量
          this.offsetX = this.lastEventOffsetX;
        }
      } else {
        if (this.offsetX - tickSpacing / 2 > maxOffsetX) {
          // 限制最大偏移量
          this.offsetX = maxOffsetX + tickSpacing / 2;
        } else if (this.offsetX - tickSpacing / 2 < minOffsetX) {
          // 限制最小偏移量
          this.offsetX = minOffsetX + tickSpacing / 2;
        }
      }

      this.drawTimeline(); // 重新绘制时间线

      const closestDate = this.getClosestDate(); // 获取最近的日期
      const closestEvent = this.getClosestEventPoint(closestDate); // 获取最近的事件点
      this.trigger("dragging", {
        currentDate: closestDate,
        eventPoint: closestEvent.eventPoint,
        leftEventPoint: closestEvent.leftEventPoint,
      }); // 触发拖动事件
    }
  }

  // 鼠标松开事件处理函数
  private onMouseUp() {
    if (this.isDragging) {
      this.isDragging = false; // 标记为停止拖动
      const closestDate = this.getClosestDate(); // 获取最近的日期
      const closestEvent = this.getClosestEventPoint(closestDate); // 获取最近的事件点
      this.trigger("dragged", {
        currentDate: closestDate,
        eventPoint: closestEvent.eventPoint,
        leftEventPoint: closestEvent.leftEventPoint,
      }); // 触发拖动事件
    }
    document.body.style.userSelect = ""; // 恢复文本选择
  }

  // 获取最近的日期
  private getClosestDate(): string {
    const { timeLineRangeDate } = this.config; // 获取时间范围
    const startDate = new Date(timeLineRangeDate[0]); // 开始日期
    const tickSpacing = this.canvas.width / 13; // 刻度间距
    const totalMonths =
      Math.floor((this.canvas.width / 2 - this.offsetX) / tickSpacing) - 6; // 总月份数
    const closestMonthDate = new Date(
      startDate.setMonth(startDate.getMonth() + totalMonths)
    ); // 最近的月份日期

    // 计算天数的偏移
    const dayOffset =
      ((this.canvas.width / 2 - this.offsetX) % tickSpacing) /
      (tickSpacing / 30); // 计算天数偏移
    const year = closestMonthDate.getFullYear();
    const month = closestMonthDate.getMonth() + 1; // 月份
    let day = Math.ceil(dayOffset); // 日期
    if (day === 0) {
      day = 1;
    }

    // 将 month 和 day 转换为字符串格式，确保是两位数
    const monthString = month < 10 ? "0" + month : month.toString();
    const dayString = day < 10 ? "0" + day : day.toString();

    return `${year}-${monthString}-${dayString}`;
  }

  // 获取最近的事件点
  private getClosestEventPoint(closestDate: string): any {
    const target = new Date(closestDate).getTime();

    // 用于eventPoint的初始化最小差值和最近事件点
    let minDiff1 = Infinity;
    let closestEvent1 = "";

    // 用于leftEventPoint的初始化最小差值和最近事件点
    let minDiff2 = Infinity;
    let closestEvent2 = "";

    // 如果没传入事件点，则返回空字符串
    if (!this.config?.eventPoints) {
      return {
        eventPoint: "",
        leftEventPoint: "",
      };
    }

    // 遍历事件点数组
    for (const event of this.config.eventPoints) {
      // 将当前事件点转换为时间戳
      const date = new Date(event).getTime();

      // 计算当前事件点与目标日期的差值
      const diff = Math.abs(date - target);
      // 如果当前差值小于最小差值，则更新最小差值和最近事件点
      if (diff < minDiff1) {
        minDiff1 = diff;
        closestEvent1 = event;
      }

      if (date - target <= 0) {
        // 计算当前事件点与目标日期的差值
        const diff = Math.abs(date - target);
        // 如果当前差值小于最小差值，则更新最小差值和最近事件点
        if (diff < minDiff2) {
          minDiff2 = diff;
          closestEvent2 = event;
        }
      }
    }

    return {
      eventPoint: closestEvent1,
      leftEventPoint: closestEvent2,
    };
  }

  // 事件监听器
  public on<K extends Events>(event: K, callback: (data: EventMap[K]) => void) {
    if (!this.eventListeners[event]) {
      this.eventListeners[event] = []; // 初始化事件监听器数组
    }
    this.eventListeners[event]!.push(callback); // 添加事件监听器
  }

  // 触发事件
  private trigger<K extends Events>(event: K, data: EventMap[K]) {
    if (this.eventListeners[event]) {
      this.eventListeners[event]!.forEach((callback) => callback(data)); // 调用事件监听器
    }
  }
}

export { Timeline as default }; // 导出Timeline类
